#include "base.h"
#include "flv.h"
#include "func.h"
#include "hylog.h"

#define AUDIO_AAC 0

//union voidint{void*p;int i;};

#include <event.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <event2/listener.h>
#include <event2/util.h>
#include <event2/event.h>
#include <event2/dns.h>
#include <event2/thread.h>
#include <evhttp.h>

#include "flv.h"
#include "func.h"
#include "H264ParseSPS.h"

static void send_metadata(struct flv_t *flv);
// 填充FLV文件头
static uint32_t flv_fillFLVH(uint8_t *pdata, uint8_t have_audio)
{
    const uint8_t flv_header[13] = {0x46, 0x4c, 0x56, 0x01, 0x01, 0x00, 0x00, 0x00, 0x09, \
                             0x00, 0x00,0x00,0x00};
/*
    const uint8_t flv_header_2[13] = {0x46, 0x4c, 0x56, 0x01, 0x05, 0x00, 0x00, 0x00, 0x09, \
                             0x00, 0x00,0x00,0x00};
*/
    memcpy(pdata, flv_header, 13);
    if(have_audio)
        pdata[4] |= 4;
        /*
        memcpy(pdata, flv_header_2, 13);
    else
        memcpy(pdata, flv_header_1, 13);
    */
    return 13;
}

// 填充FLV的tag头
static uint32_t flv_fillTH( uint8_t **pdata, uint8_t tag_type, uint32_t size, uint32_t ts )
{
    uint8_t *pd;
    pd = *pdata;
    *pd++ = tag_type;            // tag_type, 8 is audio, 9 is video
    *pd++ = (uint8_t)((size >> 16)&0xff);
    *pd++ = (uint8_t)((size >> 8)&0xff);
    *pd++ = (uint8_t)((size >> 0)&0xff);    // tag data size, exclude tag head
    *pd++ = (uint8_t)((ts >> 16)&0xff);
    *pd++ = (uint8_t)((ts >> 8)&0xff);
    *pd++ = (uint8_t)((ts >> 0)&0xff);      // ts, uint ms
    *pd++ = (uint8_t)((ts >> 24)&0xff);
    *pd++ = 0;
    *pd++ = 0;
    *pd++ = 0;                   // stream ID
    *pdata = pd;
    return 11;
}

// 填充FLV的video tag
static uint32_t flv_fillVT( uint8_t **pdata, uint8_t ftype, uint8_t nalu_c, uint8_t *nalu, uint32_t size )
{
    uint8_t *pd;
    pd = *pdata;
    *pd++ = ftype;
    *pd++ = nalu_c;
    *pd++ = 0;
    *pd++ = 0;
    *pd++ = 0;
    *pd++ = (uint8_t)((size>>24)&0xff);
    *pd++ = (uint8_t)((size>>16)&0xff);
    *pd++ = (uint8_t)((size>>8)&0xff);
    *pd++ = (uint8_t)((size>>0)&0xff);
    memcpy(pd, nalu, size);
    pd = &pd[size];
    size += 20;
    *pd++ = (uint8_t)((size>>24)&0xff);
    *pd++ = (uint8_t)((size>>16)&0xff);
    *pd++ = (uint8_t)((size>>8)&0xff);
    *pd++ = (uint8_t)((size>>0)&0xff);
    *pdata = pd;
    return size-11;
}
struct flv_t *open_flv(void *fd, enum FLVDST flvdst, struct metadata_t *meta, const char *ext_head)
{
    struct flv_t *res;
    uint8_t flv_header[13];
    res = (struct flv_t *)mycalloc(sizeof(struct flv_t),1);
    if(meta)
    {
        memcpy(&res->metadata, meta, sizeof(struct metadata_t));
    }
    else
    {
        res->metadata.m_duration=-1;
        res->metadata.m_width=640;
        res->metadata.m_height=480;
        res->metadata.m_framerate=12.5;
    }
    res->fd=fd;
    res->dst=flvdst;
    res->rate = 25;
    flv_fillFLVH(flv_header, res->metadata.m_hasautio!=0);   // 有音频
    if(flvdst==HTTP_FLV)
    {
        struct bufferevent *c=(struct bufferevent *)fd;
        char buff[1024];
        sprintf(buff,
              "HTTP/1.1 200 OK\r\n"
              "Transfer-Encoding: chunked\r\n"
              "Content-Type: video/x-flv\r\n"
              "Cache-Control: no-cache\r\n"
              "Server: HYTerminal\r\n"
              "Connection: keep-alive\r\n"
              "Expires: -1\r\n"
              "Access-Control-Allow-Credentials: true\r\n"
              "Access-Control-Allow-Origin: *\r\n"
              "Access-Control-Allow-Headers: X-Requested-With\r\n"
              "Access-Control-Allow-Methods: GET,POST,OPTIONS\r\n"
              "%s"
              "\r\n"
              ,ext_head==NULL?"":ext_head);
        bufferevent_write(c,buff, strlen(buff));
        bufferevent_write(c, "D\r\n", 3);
        bufferevent_write(c, flv_header, 13);
        bufferevent_write(c, "\r\n", 2);
        send_metadata(res);
    }
    else
    {
        union voidint vi;
        int dummy;
        vi.p = fd;
        if(lseek(vi.i, 0, SEEK_END)==0)
        {
            dummy = write(vi.i, flv_header, sizeof(flv_header));
            send_metadata(res);
        }
        else
        {
            res->audio_ts=1;
//            printf("exist file, just add data\n");
        }
        (void)dummy;
    }
    return res;
}

void close_flv(struct flv_t *flv)
{
    myfree(flv);
}

static size_t find_nalu(uint8_t *pdata, size_t size, uint8_t **frame)
{
    size_t ret=0;
    size_t i;
    for(i=0; i<size-5; i++)
    {
        if(memcmp(&pdata[i], "\0\0\0\1",4)==0)break;    // 找到nalu单元
    }
    *frame=NULL;
    if(i==size-5)
        return ret;    // 没找到nalu单元
    *frame=&pdata[i+4];
    ret = size-i-4;
    return ret;
}

static void output(struct flv_t *flv, uint8_t *pdata, size_t size)
{
    if(flv->dst==HTTP_FLV)
    {
        struct bufferevent *c=(struct bufferevent *)flv->fd;
        char lstr[32];
        size_t len;
        sprintf(lstr, "%X\r\n", (int)size);
        len = strlen(lstr);
        bufferevent_write(c, lstr, len);
        bufferevent_write(c, pdata, size);
        bufferevent_write(c, "\r\n", 2);
    }
    else
    {
        union voidint vi;
        vi.p = flv->fd;
        write(vi.i, pdata, (unsigned int)size);
    }
}

void send_video(struct flv_t *flv, uint8_t *pdata, size_t size)
{
    uint8_t *pframe;
    uint8_t *outp, *ptr;
    size_t outl, off;
    size_t flen;

    if(pdata==NULL)return;   // 缓冲区不能为空
    if(flv==NULL)return;     // 句柄不能为空
    if(size<5)return;        // 帧长不够
    flen=find_nalu(pdata, size, &pframe);
    if(flen==0)return;
    if(pframe[0]==0x67)      // PPS
    {
        size_t flen1, flen2;
        sps_info_struct info;
        uint8_t *pframe1, *pframe2;
        flen1 = find_nalu(pframe, flen, &pframe1);
        if(flen1==0)return;  // 不是SPS
        flen2 = find_nalu(pframe1, flen1, &pframe2);
        if(flen < flen1+4)return;
        flen -= flen1+4;
        if(h264_parse_sps(pframe, (unsigned int)flen, &info))
        {
            flv->rate = info.fps;
        }
        if(flen1 < flen2+4)return;
        flen1 -= flen2+4;
        outl = flen+flen1+16;
        outp = (uint8_t*)myalloc(outl+15);
        ptr = outp;
        // flv tag header
        off = flv_fillTH(&ptr, 9, (uint32_t)outl, flv->video_ts);
        // flv video tag header
        outp[off++] = 0x17; //key frame, AVC
        outp[off++] = 0x00; //avc sequence header
        outp[off++] = 0x00; // composit time
        outp[off++] = 0x00; // composit time
        outp[off++] = 0x00; // composit time
        // flv VideoTagBody --AVCDecoderCOnfigurationRecord
        outp[off++] = 0x01; // configurationversion
        outp[off++] = (uint8_t)(pframe[1]); // avcprofileindication
        outp[off++] = (uint8_t)(pframe[2]); // profilecompatibilty
        outp[off++] = (uint8_t)(pframe[3]); // avclevelindication
        outp[off++] = 0xff; // reserved + lengthsizeminusone
        outp[off++] = 0xe1; // numofsequenceset
        outp[off++] = (uint8_t)((flen>>8)&0xff);
        outp[off++] = (uint8_t)((flen>>0)&0xff);
        memcpy(&outp[off], pframe, flen);
        off += flen;
        outp[off++] = 0x01; // numofpictureset
        outp[off++] = (uint8_t)((flen1>>8)&0xff);
        outp[off++] = (uint8_t)((flen1>>0)&0xff);
        memcpy(&outp[off], pframe1, flen1);
        off += flen1;
        outl += 11;
        outp[off++] = (uint8_t)((outl>>24)&0xff);
        outp[off++] = (uint8_t)((outl>>16)&0xff);
        outp[off++] = (uint8_t)((outl>>8)&0xff);
        outp[off++] = (uint8_t)((outl>>0)&0xff);
        output(flv, outp, outl+4);
        myfree(outp);
        if(flen2==0)return;  // 关键帧
        send_video(flv, pframe2, flen2);
    }
    else if(pframe[0]==0x06)
    {
        send_video(flv, pframe, flen);
    }
    else if(pframe[0]==0x65 || pframe[0]==0x61)
    {
        flv->video_ts += (uint32_t)(1000/flv->rate);
        outl = flen+9;
        outp = (uint8_t*)myalloc(outl+15);
        ptr = outp;
        // flv tag header
        off = flv_fillTH(&ptr, 9, (uint32_t)outl, flv->video_ts);
        flv_fillVT(&ptr, pframe[0]==0x61?0x27:0x17, 0x01, pframe, (uint32_t)flen);
        output(flv, outp, outl+15);
        myfree(outp);
    }
}

void send_audio(struct flv_t *flv, uint8_t *pdata, size_t size)
{
    uint8_t *outp, *ptr;
    size_t outl;
    if(pdata==NULL)return;   // 缓冲区不能为空
    if(flv==NULL)return;     // 句柄不能为空
    if(size<4)return;        // 帧长不够
    if(AUDIO_AAC)
    {
        outl = size;
        if(flv->audio_ts==0)    // 属于AAC编码，开始写AAC头
        {
            uint16_t type;
            outp = (uint8_t*)myalloc(19);
            ptr = outp;
            flv_fillTH(&ptr, 8, 4, flv->audio_ts);
            *ptr++ = 0xAE;
            *ptr++ = 0x00;
            type = 2<<11;
            type |= 4<<7;
            type |= 1<<3;
            *ptr++ = type&0xff;
            *ptr++ = (type>>8)&0xff;
            *ptr++ = 0;
            *ptr++ = 0;
            *ptr++ = 0;
            *ptr++ = 15;
            output(flv, outp, 19);
            myfree(outp);
        }
        outp = (uint8_t*)myalloc(outl+17);   // TAG_HEADER:11 bytes, PRE_TAG_LEN:4, AUDIO_TYPE:1 byte, AAC_CONF:1 byte, total 17 bytes
        ptr = outp;
        flv_fillTH(&ptr, 8, outl+2, flv->audio_ts);  // TAG_HEADER, 11 bytes, body_len, body+audio type+aac conf
    }
    else
    {
        outl = (uint8_t)pdata[2];
        outl *= 2;
        if(outl+4 != size)return;   // 帧长不对
        outp = (uint8_t*)myalloc(outl+16);
        ptr = outp;
        flv_fillTH(&ptr, 8, outl+1, flv->audio_ts);
    }
    flv->audio_ts += 64;    // 320点/帧，8k采样率
    if(AUDIO_AAC)
    {
        *ptr++ = 0xAE;       // audio type
        *ptr++ = 0x01;       // aac conf
        memcpy(ptr, pdata, outl);
        ptr = &ptr[outl];
        outl += 13;
    }
    else
    {
        *ptr++ = 0x1E;   // ADPCM
        memcpy(ptr, &pdata[4], outl);
        ptr = &ptr[outl];
        outl += 12;
    }
    *ptr++ = (uint8_t)((outl>>24)&0xff);
    *ptr++ = (uint8_t)((outl>>16)&0xff);
    *ptr++ = (uint8_t)((outl>>8)&0xff);
    *ptr++ = (uint8_t)((outl>>0)&0xff);
    output(flv, outp, outl+4);
    myfree(outp);
}

void m_fill_double(int *off, uint8_t *dst, double val)
{
    uint8_t *ptr;
    int _off=*off;
    int i;
    ptr = (uint8_t*)&val;
    dst[_off++] = 0;
    for(i=0; i<8; i++)
    {
        dst[_off+i] = ptr[7-i];
    }
    *off = _off+8;
}

void m_fill_string(int *off, uint8_t *dst, char*val)
{
    int _off=*off;
    size_t len=strlen(val);
    dst[_off++] = 2;
    dst[_off++] = (len>>16)&0xff;
    dst[_off++] = (len>>0)&0xff;
    memcpy(&dst[_off], val,len);
    *off = (int)(len)+_off;
}

void m_fill_bool(int *off, uint8_t *dst, int val)
{
    int _off=*off;
    dst[_off++] = 1;
    dst[_off++] = val!=0;
    *off = _off;
}

void m_fill_key(int *off, uint8_t *dst, char*val)
{
    int _off;
    size_t len=strlen(val);
    _off = *off;
    dst[_off++] = (len>>16)&0xff;
    dst[_off++] = (len>>0)&0xff;
    memcpy(&dst[_off], val,len);
    *off = (int)(len)+_off;
}

void send_metadata(struct flv_t *flv)
{
    int off, dsize;
    char buff[64];
    char *p;
    uint8_t *ptr;
    uint8_t *mbuff=(uint8_t*)mycalloc(512,1);
    off=0;
    ptr = mbuff;
    ptr[off++] = 0x12;    // type is metadata
    off += 3;             // metadata size
    off += 4;             // metadata time stamp
    off += 3;             // stream id
    ptr[off++] = 0x02;    // AMF1 type is 2(string)
    
    ptr[off++] = 0x00;
    ptr[off++] = 0x0A;    // AMF1 string length
    
    memcpy(&ptr[off], "onMetaData", 10);   // AMF1 string "onMetaData"
    off += 10;
    
    ptr[off++] = 0x08;   // AMF2 type is array
    
    ptr[off++] = 0;
    ptr[off++] = 0;
    ptr[off++] = 0;
    ptr[off++] = 0x0d;   // AMF2 array size is 13
    
    m_fill_key(&off, ptr, "duration");
    m_fill_double(&off, ptr, flv->metadata.m_duration);
    
    m_fill_key(&off, ptr, "width");
    m_fill_double(&off, ptr, flv->metadata.m_width);
    
    m_fill_key(&off, ptr, "height");
    m_fill_double(&off, ptr, flv->metadata.m_height);
    
    m_fill_key(&off, ptr, "framerate");
    m_fill_double(&off, ptr, flv->metadata.m_framerate);
    
    m_fill_key(&off, ptr, "videodatarate");
    m_fill_double(&off, ptr, flv->metadata.m_videodatarate);
    
    m_fill_key(&off, ptr, "audiodatarate");
    m_fill_double(&off, ptr, flv->metadata.m_audiodatarate);
    
    m_fill_key(&off, ptr, "videocodecid");
    m_fill_double(&off, ptr, flv->metadata.m_videocodecid);
    
    m_fill_key(&off, ptr, "audiocodecid");
    m_fill_double(&off, ptr, flv->metadata.m_audiocodecid);
    
    m_fill_key(&off, ptr, "audiosamplerate");
    m_fill_double(&off, ptr, flv->metadata.m_audiosamplerate);
    
    m_fill_key(&off, ptr, "audiosamplesize");
    m_fill_double(&off, ptr, flv->metadata.m_audiosamplesize);
    
    m_fill_key(&off, ptr, "stereo");
    m_fill_bool(&off, ptr, flv->metadata.m_stereo);
    
    m_fill_key(&off, ptr, "title");
    m_fill_string(&off, ptr, "HangYi TECH video");

    m_fill_key(&off, ptr, "metadatacreator");
    m_fill_string(&off, ptr, "HangYi TECH ship Terminal, at Aug 2022");

    sprintf(buff, "flv-video-chn%d", flv->metadata.m_chn+1);
    m_fill_key(&off, ptr, "comment");
    m_fill_string(&off, ptr, buff);
    
    m_fill_key(&off, ptr, "CanSeekToEnd");
    m_fill_bool(&off, ptr, 0);

    p = localtimet2str(time(NULL));
    m_fill_key(&off, ptr, "metadatadate");
    m_fill_string(&off, ptr, p);
    myfree(p);

    ptr[off++] = 0;
    ptr[off++] = 0;
    ptr[off++] = 9;
    dsize = off-11;
    ptr[1] = (dsize>>16)&0xff;
    ptr[2] = (dsize>>8)&0xff;
    ptr[3] = (dsize)&0xff;
    
    dsize=off;
    ptr[off++] = (dsize>>24)&0xff;
    ptr[off++] = (dsize>>16)&0xff;
    ptr[off++] = (dsize>>8)&0xff;
    ptr[off++] = (dsize>>0)&0xff;
    output(flv, ptr, off);
    myfree(ptr);
}
